﻿using System.ComponentModel;
using System.Collections.ObjectModel;
using System;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Text;
using System.Threading;
using System.Web;
using ZenBridge.ZfApi;
using ZenBridge;

namespace ZenBridge
{
    /// <summary>
    /// Helper inherited from generated ZfApiSoapClient service proxy class.
    /// Provides login sequence, manages authentication token and exposes
    /// methods for the app.
    /// </summary>
    public class ZenfolioClient : ZfApiSoapClient, IZenBridgeNotifier
    {
        private string _token;
        private string _loginName;

        public static ManualResetEvent alldone = new ManualResetEvent(false);
        const int DefaultTimeout = 2 * 60 * 1000; //2 minute timeout;

        private static void TimeoutCallback(object state, bool timedOut)
        {
            if (timedOut)
            {
                HttpWebRequest request = state as HttpWebRequest;
                if (request != null)
                {
                    request.Abort();
                }
            }
        }

        private Group group = new Group();
        public Group Group
        {
            get
            {
                return group;
            }
            set
            {
                group = value;
                OnPropertyChanged("Group");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public event EventHandler<NotifyUserEventArgs> NotifyUser;

        public event EventHandler<PhotoUploadProgressChangedEventArgs> PhotoUploadProgressChanged;

        public event EventHandler<PhotoUploadCompleteEventArgs> PhotoUploadComplete;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            
            if (handler != null)
            {
                handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected virtual void OnNotifyUser(string message)
        {
            EventHandler<NotifyUserEventArgs> handler = NotifyUser;
            
            if (handler != null)
            {
                handler.Invoke(this, new NotifyUserEventArgs(message));
            }
        }

        protected virtual void OnPhotoUploadProgressChanged(long bytesDone, FileInfo fileInfo)
        {
            EventHandler<PhotoUploadProgressChangedEventArgs> handler = PhotoUploadProgressChanged;

            if (handler != null)
            {
                handler.Invoke(this, new PhotoUploadProgressChangedEventArgs(bytesDone, fileInfo));
            }
        }

        protected virtual void OnPhotoUploadComplete(FileInfo fileInfo)
        {
            EventHandler<PhotoUploadCompleteEventArgs> handler = PhotoUploadComplete;

            if (handler != null)
            {
                handler.Invoke(this, new PhotoUploadCompleteEventArgs(fileInfo));
            }
        }
        
        public ZenfolioClient()
        {
        }

        /// <summary>
        /// Gets login name of current user
        /// </summary>
        public string LoginName
        {
            get { return _loginName; }
        }

        /// <summary>
        /// Gets current user token.
        /// </summary>
        public string Token
        {
            get { return _token; }
        }

        public bool IsAuth
        {
            get
            {
                if (_token == null)
                {
                    return false;
                }
                return true;
            }
        }

        /// <summary>
        /// Computes salted data hash
        /// </summary>
        /// <param name="data">Data to hash</param>
        /// <param name="salt">Salt</param>
        /// <returns>Computed SHA-256 hash of salt+data pair</returns>
        private static byte[] HashData(byte[] salt, byte[] data)
        {
            byte[] buffer = new byte[data.Length + salt.Length];
            salt.CopyTo(buffer, 0);
            data.CopyTo(buffer, salt.Length);
            return new SHA256Managed().ComputeHash(buffer);
        }

        /// <summary>
        /// Logs into Zenfolio API
        /// </summary>
        /// <param name="loginName">User's login name</param>
        /// <param name="password">User's password</param>
        /// <returns>True if login was successful, false otherwise.</returns>
        public bool Login(string loginName, string password)
        {
            using (OperationContextScope scope = new OperationContextScope(InnerChannel))
            {
                HttpRequestMessageProperty httpProperty = new HttpRequestMessageProperty();
                httpProperty.Headers.Add(HttpRequestHeader.UserAgent, "ZenBridge v.1");
                OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] =
                    httpProperty;
                // Get API challenge
                AuthChallenge ch = this.GetChallenge(loginName);

                // Extract and hash password bytes
                byte[] passwordHash = HashData(ch.PasswordSalt, Encoding.UTF8.GetBytes(password));

                // Compute secret proof
                byte[] proof = HashData(ch.Challenge, passwordHash);

                // Authenticate
                try
                {
                    _token = this.Authenticate(ch.Challenge, proof);
                    if (_token != null)
                    {
                        _loginName = loginName;
                        //Group = LoadUserGroups();
                        return true;
                    }
                }
                catch
                {
                    // Swallow all exceptions and return false
                }
                return false;
            }
        }

        public void LoadUserGroups()
        {
            if (IsAuth)
            {
                OnNotifyUser("Loading galleries...");
                using (OperationContextScope scope = new OperationContextScope(InnerChannel))
                {
                    HttpRequestMessageProperty httpProperty = new HttpRequestMessageProperty();
                    httpProperty.Headers.Add("X-Zenfolio-Token", _token);
                    httpProperty.Headers.Add(HttpRequestHeader.UserAgent, "ZenBridge v.1");
                    OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] =
                        httpProperty;
                    Group = LoadGroupHierarchy(_loginName);
                }
                OnNotifyUser("Galleries loaded.");
            }
        }

        public IAsyncResult UploadUserPhoto(FileInfo fileInfo, PhotoSet uploadGallery)
        {
            if (uploadGallery.UploadUrl == null)
            {
                return null;
            }

            SynchronizationContext context = SynchronizationContext.Current;

            try
            {                
                HttpWebRequest uploadRequest = (HttpWebRequest)WebRequest.Create(uploadGallery.UploadUrl + "?filename=" + 
                   HttpUtility.UrlEncode(fileInfo.Name));
                uploadRequest.Timeout = DefaultTimeout;
                uploadRequest.ContentLength = fileInfo.Length;
                uploadRequest.ContentType = GetMimeType(fileInfo.Name);
                uploadRequest.UserAgent = "ZenBridge v.1";
                uploadRequest.Headers.Add("X-Zenfolio-Token", _token);
                uploadRequest.Method = "POST";
                WebClient client = new WebClient();
                return uploadRequest.BeginGetRequestStream(
                    delegate(IAsyncResult requestStreamResult)
                    {
                        Stream requestStream = uploadRequest.EndGetRequestStream(requestStreamResult);
                        
                            using (FileStream fs = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
                            {
                                using (BinaryReader br = new BinaryReader(fs))
                                {
                                    byte[] buffer = new byte[100];
                                    int bytesRead;

                                    do
                                    {
                                        bytesRead = br.Read(buffer, 0, buffer.Length);
                                        requestStream.Write(buffer, 0, bytesRead);
                                        context.Post(delegate
                                        {
                                            OnPhotoUploadProgressChanged(bytesRead, fileInfo);
                                        }, null);
                                    } while (bytesRead > 0);
                                }
                            }

                        requestStream.Close();
                        

                        uploadRequest.BeginGetResponse(
                            delegate(IAsyncResult responseResult)
                            {
                                HttpWebResponse response = (HttpWebResponse)uploadRequest.EndGetResponse(responseResult);

                                if (response.StatusCode == HttpStatusCode.OK)
                                {
                                    context.Post(delegate
                                    {
                                        OnPhotoUploadComplete(fileInfo);
                                        OnNotifyUser(fileInfo.Name + " successfully uploaded\nto " + uploadGallery.Title);
                                    }, null);
                                }
                                else
                                {
                                    context.Post(delegate
                                    {
                                        OnNotifyUser("Error uploading " + fileInfo.Name + ":\n" + response.StatusDescription);
                                    }, null);
                                }
                                response.Close();
                                alldone.Set();
                            }, null
                            );
                    }, null
                    );
            }
            catch (Exception exception)
            {
                context.Post(delegate
                {
                    OnNotifyUser(exception.Message);
                }, null);
                return null;
            }
        }

        public bool Logout()
        {
            _token = null;
            Group = new Group();
            _loginName = null;
            return true;
        }

        private string GetMimeType (string fileName)
        {
            string mimeType = "application/unknown";
            string ext = System.IO.Path.GetExtension(fileName).ToLower();
            Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext);
            if (regKey != null && regKey.GetValue("Content Type") != null)
            mimeType = regKey.GetValue("Content Type").ToString();
            return mimeType;
        }

    }
}
